/*
 Copyright 2012 rpablossanchez@gmail.com
 

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

package org.bn.utils;

import java.io.IOException;
import java.io.OutputStream;
import org.bn.types.BitString;

/**
 *
 * @author Ronald
 */
public class BitOutputStream extends OutputStream {
    int currentBit = 0;
    OutputStream os = null;
    byte oneOctetBuffer = 0;
            
    public BitOutputStream(OutputStream os) {
        this.os = os;
    }
    
    
    @Override
    public void write(int b) throws IOException {
       
        if(currentBit==0) {
            os.write(b);
        }
        else {
            byte nBt = (byte) (oneOctetBuffer | ( b >> currentBit ));
            os.write(nBt);
            oneOctetBuffer = (byte)(b << (8 - currentBit));
        }
    }
   
   @Override
   public  void write(byte b[], int off, int len) throws IOException {
       if (currentBit == 0)
           os.write(b, off, len);
       else
           super.write(b,off,len);    
   }
   
   public void writeBit(boolean value) throws IOException {
        writeBit (value ? 1:0);
   }
   
   public void writeBit(int bit) throws IOException {    
        if(currentBit < 8 && currentBit > 0) {
            if(bit!=0) {
                oneOctetBuffer |= (byte)(0x80 >> currentBit);
            }
        }
        else {
            oneOctetBuffer = (byte) (bit == 0 ? 0:0x80);
        }        
        currentBit++;
        if(currentBit>=8) {
            os.write(oneOctetBuffer);
            oneOctetBuffer = 0;
            currentBit = 0;
        }
    }
   
    public synchronized void writeBits(long bt, int n) throws IOException {
        if (n <=0) return;
        if (n < 64)
            bt &= ~(~0L << n);
        int bitsAvailableInCurrentOctet = 8 - currentBit;
        int headingBits = Math.min(bitsAvailableInCurrentOctet, n);
        int trailingBits = (n-headingBits) % 8;
        int noctets = (n-headingBits) / 8;
        
        //heading bits
        oneOctetBuffer |= (( bt >>> (8*noctets+trailingBits) ) << (bitsAvailableInCurrentOctet-headingBits));
        currentBit = (currentBit+headingBits) % 8;
            if(currentBit==0) {
                os.write(oneOctetBuffer);
                oneOctetBuffer = 0;
            }
        //aligned octets
        for (; noctets > 0; noctets--) 
            os.write((int) (0xFF & (bt>>>((noctets-1)*8+trailingBits))) );
        //trailing bits
        if (trailingBits > 0) {
            oneOctetBuffer = (byte) (bt << (8-trailingBits));
            currentBit += trailingBits;
        }

    
    }
    
    public void writeBits(int bt, int n) throws IOException {
        writeBits((long)bt, n);
    }
   
    public int getTrailBitsCnt() {
        return currentBit;
    }
    
    public void writeBitString(BitString bitString) throws IOException {
        if (bitString.getLength() == 0) 
            return;
        write(bitString.getValue(),0,bitString.getLength()-1);
        int trailBits = bitString.getTrailBitsCnt();
        writeBits( 0xff & (bitString.getValue()[bitString.getLength()-1] >> (8-trailBits)), trailBits);
    }
    
    public void align() throws IOException {
        if (currentBit != 0) {
            os.write(oneOctetBuffer);
            oneOctetBuffer = 0;
        }
        currentBit = 0;
    }

    /** Flush the stream, by aligning and then flushing the underlying stream
     *
     * @throws IOException
     */
    @Override
    public void flush() throws IOException {
        align();
        super.flush();
    }
    
    
}
